<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
* ClassName: Unlock_ip
*
* description...
*
* @author hejigui <hejigui@soften.cn>
*
*/
class Unlock_ip extends CI_Controller {
	/**
	* php_bin
	*
	* @var string
	*/
	private $_php_bin = '/usr/local/php/bin/php';
	private $_cookie;

	/**
	* __construct
	*
	* @access public
	* @return string $mixed
	*/
	public function __construct() {
        parent::__construct();
		ini_set("display_errors","on");
		error_reporting(E_ALL & ~E_NOTICE);
		set_time_limit(0);
	}



	/**
	*  主进程持续执行2分钟..到期后结束中断 中间只检查需要解封的IP.并启动子进程解封
	* 1 获取需要解封的IP列表
	* 2 遍历 需解封IP列表,
	* 3 检查系统中现有可用IP数..如果可用数超过5个..则不需要再解封..以节省解码接口请求次数
	* 4 检查当前IP的距离拨号剩余时间,小于 每次抓取请求所需时间 时,无需再解封的问题
	* 5 检查当前IP是否已有其它进程在解封.如果有.则continue
	* 6 将当前ip标识正在解封..并启动子进程开始解封..
	* 7 子进程执行完毕(不论是否成功与否).将解除此ip正在解封标识
	*
	*
	* 注意第4步
	* 此处时间不能用配置文件中的 adsl_life,adsl_life的意义 是与抓取时 curl_timeout 相对应的,为了解决IP正在用.而机器已拨号的问题，比如场景：
	* 一次请求需要20s.如果我们取IP的时候.不做限定.那有可能取到已经过了115s的IP.这样的话因此机器马上就会换IP..注定此次请求会出错..增加失败率
	*
	* @access public
	* @return string $mixed
	*/
	public function multi_start() {
		//每次抓取请求最长可用时间
		$crawl_curl_timeout = $this->config->item('curl_timeout');
		$crawl_curl_timeout = $crawl_curl_timeout - 10;


		//可用ip数  当超过此数值..系统将不再解封IP
		$usable_ip_num = $this->config->item('usable_ip_num');

		$this->load->model('universal/mobility_ip_model');

		$start_time = $curr_time = time();
		$listing_end_time = time()+120;

		while ($curr_time < $listing_end_time){
			//1 获取需要解封的IP列表
			$locked_ip_list = array();
			if(false == ($locked_ip_list = $this->mobility_ip_model->get_locked_list('sina'))){
				sleep(1);
				continue;
			}

			//2 遍历 需解封IP列表,
			foreach ($locked_ip_list as $ip_info) {
				//3 检查系统中现有可用IP数..如果可用数超过4个..则不需要再解封..以节省解码接口请求次数
				if($usable_ip_num < ($usable_num = $this->mobility_ip_model->get_normal_count('sina'))){
					break;
				}

				//如果此IP的入库时间距现在.现过了110s..则说明马上会重新拨号..所以没有必要解封
				//if((time()-$ip_info['ctime']) >= 110) continue;

				//4 此处是为了解决:当ip的距离拨号剩余时间,小于 每次抓取请求所需时间 时,无需再解封的问题
				//if((120 - (time()-$ip_info['ctime'])) < $crawl_curl_timeout) continue;

				//5 检查当前IP是否已有其它进程在解封.如果有.则continue
				if($this->is_unlocking($ip_info['source'])) continue;

				//6 将当前ip标识正在解封..并启动子进程开始解封..
				$this->add_unlocking($ip_info['source']);

				$file = "/home/www/crawl/public/index.php unlock_ip unlock {$ip_info['id']} {$ip_info['ip']}:{$ip_info['port']} {$ip_info['source']}";
				$command = $this->_php_bin.' '.$file.' >> /root/unlock.log &';
				//echo $command."\n";
				exec($command);
			}

			//防止重复解锁
			sleep(3);

			//记录当前时间.
			$curr_time = time();
		}

	}



	/**
	* 单进程解封所有被封IP
	*
	* 注意
	* 此处时间不能用配置文件中的 adsl_life,adsl_life的意义 是与抓取时 curl_timeout 相对应的,为了解决IP正在用.而机器已拨号的问题，比如场景：
	* 一次请求需要20s.如果我们取IP的时候.不做限定.那有可能取到已经过了115s的IP.这样的话因此机器马上就会换IP..注定此次请求会出错..增加失败率
	*
	* @access public
	* @return string $mixed
	*/
	public function start() {
		//每次抓取请求最长可用时间
		$crawl_curl_timeout = $this->config->item('curl_timeout');

		$this->load->model('universal/mobility_ip_model');
		$locked_ip_list = $this->mobility_ip_model->get_locked_list('sina');
		//var_dump($locked_ip_list);


		foreach ($locked_ip_list as $ip_info) {
			//如果此IP的入库时间距现在.现过了110s..则说明马上会重新拨号..所以没有必要解封
			//if((time()-$ip_info['ctime']) >= $this->_keeplive_time) continue;

			//此处是为了解决:当ip的距离拨号剩余时间,小于 每次抓取请求所需时间 时,无需再解封的问题
			if((120 - (time()-$ip_info['ctime'])) < $crawl_curl_timeout) continue;

			$ip = "{$ip_info['ip']}:{$ip_info['port']}";
			$this->unlock($ip_info['id'],$ip,$ip_info['source']);
		}
	}
	
	/**
	 * 获取需要解封的cookie，一次获取一个
	 * @return boolean|unknown
	 */
	public function unlock_cookie(){
		
		$this->load->database();
		$this->load->library('crawl_abstract');
		
		$cookie = $this->crawl_abstract->get_unlock_cookie("sina");
		
		if( is_array($cookie) && array_key_exists("error", $cookie) ) {
			return false;
		}else{
			
			$rand_string = date('YmdHis').'_'.rand(1000,9999);
			$cookie_file_name = "{$rand_string}.txt";
	
			//1 获取验证码图片
			$get_yzm_parameters['url'] = 'http://s.weibo.com/ajax/pincode/pin?type=sass&ts='.time();
			$get_yzm_parameters['cookie_file_name'] = APPPATH."cache/yzm/".$cookie_file_name;
			$get_yzm_parameters['cookie_str'] = "ok";
			$get_yzm_parameters['cookie'] = $cookie['cookie_str'];
			
			$this->load->library('unlock_sina_library');
			$yzm_content_response = $this->unlock_sina_library->send_http($get_yzm_parameters);
	
			//1.a 获取验证码内容出错,可能会是因为adsl服务器ip已变更/或其它网络原因,则不再请求uu接口,不请求解封..只解除正在解封标识,记录日志
			if(is_array($yzm_content_response)){
	
				var_dump("get_yzm_content fail! source:{$source}--http_code:{$yzm_content_response['http_code']}");
				return false;
			}
	
			//2 用拿到的验证码图片内容,换取 验证码的值
			$dest_file = APPPATH."cache/yzm/{$rand_string}_code.png";
			file_put_contents($dest_file,$yzm_content_response);
	
			//破解验证码平台
			$yzm_platform = $this->config->item('yzm_platform');
	
			$yzm_value = $this->unlock_sina_library->get_yzm($dest_file,$yzm_platform);
	
			//2.a 获取验证码出错,可能是因为接口没费用/或网络原因失败..则不再请求解封..只解除正在解封标识,记录日志
			if(false == ($yzm_value)){
				$yzm_error_info = $this->unlock_sina_library->_error;
				
				var_dump($yzm_error_info);
				return false;
			}
	
			//3 能正确拿到验证码，则请求解封...
			$post_data['secode'] = $yzm_value;
			$post_data['type'] = 'sass';
			$post_data['pageid'] = 'weibo';
			$post_data['_t'] = 0;
			var_dump("source:{$source}--yzm:{$yzm_value}");
	
			$post_yzm_parameters['url'] = 'http://s.weibo.com/ajax/pincode/verified?__rnd='.time();
			$post_yzm_parameters['post_data'] = $post_data;
			$post_yzm_parameters['cookie_file_name'] = APPPATH."cache/yzm/".$cookie_file_name;
			$post_yzm_parameters['cookie_str'] = "ok";
			$post_yzm_parameters['cookie'] = $cookie['cookie_str'];
			
			$unlock_response = $this->unlock_sina_library->send_http($post_yzm_parameters);
	
			if(is_array($unlock_response)){//4 如果 post 解封请求失败..则 .只解除正在解封标识,记录日志
				var_dump("unlock post yzm source:{$ip_info['source']}--http_code:{$unlock_response['http_code']}");

				return false;
			}
	
			$unlock_array = json_decode($unlock_response,true);
			
			var_dump("source:{$source}--{$unlock_response}");
	
			//5 如果返回的code不是标识成功的..只解除正在解封标识,记录日志
			if($unlock_array['code'] != 100000){
				return false;
			}
			print_r($cookie);
			$rs = $this->crawl_abstract->unlock_cookie($cookie['id']);
		}
	}


	/**
	* unlock
	* 1 获取验证码图片
	* 		1.a 获取验证码内容出错,可能会是因为adsl服务器ip已变更/或其它网络原因,则不再请求uu接口,不请求解封..只解除正在解封标识,记录日志
	* 2 用拿到的验证码图片内容,换取 验证码的值
	*		2.a 获取验证码出错,可能是因为接口没费用/或网络原因失败..则不再请求解封..只解除正在解封标识,记录日志
	* 3 能正确拿到验证码，则请求解封...
	* 4 如果 post 解封请求失败..则 .只解除正在解封标识,记录日志
	* 5 如果返回的code不是标识成功的..只解除正在解封标识,记录日志
	* 6 如果解封返回的code标识成功..则更新ip状态...
	*
	* @access public
	* @param array $ip_info  ip信息,格式如：array('id'=>1203433,'ip'=>'223.241.161.28','port'=>'20707','source'=>'1') [Optional]
	* @return string $mixed
	*/
	public function unlock($id,$ip,$source) {
		$ip_info['id'] = $id;
		$ip_info['source'] = $source;
		list($ip_info['ip'],$ip_info['port']) = explode(':',$ip);

		//$ip_info = array('id'=>1206165,'ip'=>'223.241.161.103','port'=>'20707','source'=>'1');
		if(empty($ip_info['id']) || empty($ip_info['source']) || empty($ip_info['ip']) || empty($ip_info['port'])){
			echo "unlock param is error !! ip_info_string:".json_encode($ip_info)."\n\n";

			$this->del_unlocking($ip_info['source']);//删除正在解封标识
			return false;
		}

		$log_data['start_time'] = time();
		$log_data['source'] = $ip_info['source'];
		$log_data['ip'] = $ip_info['ip'];
		$log_data['port'] = $ip_info['port'];
		$this->load->model('universal/unlock_log_model');


		$proxy = "{$ip_info['ip']}:{$ip_info['port']}";
		$rand_string = date('YmdHis').'_'.rand(1000,9999);
		$cookie_file_name = "{$rand_string}.txt";

		//1 获取验证码图片
		$get_yzm_parameters['url'] = 'http://s.weibo.com/ajax/pincode/pin?type=sass&ts='.time();
		$get_yzm_parameters['proxy'] = $proxy;
		$get_yzm_parameters['cookie_file_name'] = APPPATH."cache/yzm/".$cookie_file_name;
		
		$this->load->library('unlock_sina_library');
		$yzm_content_response = $this->unlock_sina_library->send_http($get_yzm_parameters);


		//1.a 获取验证码内容出错,可能会是因为adsl服务器ip已变更/或其它网络原因,则不再请求uu接口,不请求解封..只解除正在解封标识,记录日志
		if(is_array($yzm_content_response)){
			$yzm_content_error = "get_yzm_content is error !! http_info:".json_encode($yzm_content_response);
			$log_data['unlock_log'] = $yzm_content_error;

			var_dump("get_yzm_content fail! source:{$source}--http_code:{$yzm_content_response['http_code']}");

			$this->del_unlocking($ip_info['source']);//删除正在解封标识
			return $this->unlock_log_model->record_log($log_data);
		}


		//2 用拿到的验证码图片内容,换取 验证码的值
		$dest_file = APPPATH."cache/yzm/{$rand_string}_code.png";
		file_put_contents($dest_file,$yzm_content_response);

		$log_data['upload_time'] = time();//开始上传验证码时间
		$log_data['yzm_img_name'] = $dest_file;


		//破解验证码平台
		$yzm_platform = $this->config->item('yzm_platform');

		$yzm_value = $this->unlock_sina_library->get_yzm($dest_file,$yzm_platform);
		$log_data['get_yzm_val_time'] = time();//取得验证码值时间


		//2.a 获取验证码出错,可能是因为接口没费用/或网络原因失败..则不再请求解封..只解除正在解封标识,记录日志
		if(false == ($yzm_value)){
			$yzm_error_info = $this->unlock_sina_library->_error;
			$log_data['unlock_log'] = $yzm_error_info;
			var_dump($yzm_error_info);


			$this->del_unlocking($ip_info['source']);//删除正在解封标识
			return $this->unlock_log_model->record_log($log_data);
		}


		//3 能正确拿到验证码，则请求解封...
		$post_data['secode'] = $yzm_value;
		$post_data['type'] = 'sass';
		$post_data['pageid'] = 'weibo';
		$post_data['_t'] = 0;
		var_dump("source:{$source}--yzm:{$yzm_value}");

		$post_yzm_parameters['url'] = 'http://s.weibo.com/ajax/pincode/verified?__rnd='.time();
		$post_yzm_parameters['proxy'] =  $proxy;
		$post_yzm_parameters['post_data'] = $post_data;
		$post_yzm_parameters['cookie_file_name'] = APPPATH."cache/yzm/".$cookie_file_name;
		
		$unlock_response = $this->unlock_sina_library->send_http($post_yzm_parameters);
		$log_data['unlock_time'] = time();//提交验证码值时间
		$log_data['yzm_val'] = $yzm_value;


		if(is_array($unlock_response)){//4 如果 post 解封请求失败..则 .只解除正在解封标识,记录日志
			$log_data['unlock_log'] = "unlock post_yzm is error !! http_info:".json_encode($unlock_response);
			var_dump("unlock post yzm source:{$ip_info['source']}--http_code:{$unlock_response['http_code']}");

			$this->del_unlocking($ip_info['source']);//删除正在解封标识
			return $this->unlock_log_model->record_log($log_data);
		}


		$unlock_array = json_decode($unlock_response,true);
		$log_data['unlock_log'] = $unlock_response;
		$log_data['unlock_rs'] = $unlock_array['code'];
		var_dump("source:{$source}--{$unlock_response}");



		//5 如果返回的code不是标识成功的..只解除正在解封标识,记录日志
		if($unlock_array['code'] != 100000){
			$this->del_unlocking($ip_info['source']);//删除正在解封标识
			return $this->unlock_log_model->record_log($log_data);
		}

		//6 如果解封返回的code标识成功..则更新ip状态...
		$this->load->model('universal/mobility_ip_model');
		$this->mobility_ip_model->update_status($ip_info['id'],'sina');

        // 存储正确的code, 用来做数据训练
        //$dest_file = APPPATH."cache/yzm/{$rand_string}_code.txt";
        //file_put_contents($dest_file,$yzm_value);

		$this->unlock_log_model->record_log($log_data);
		return $this->del_unlocking($ip_info['source']);//删除正在解封标识
	}


	/**
	* 检查是否正在执行解锁
	* 此处不需记录 ip,因为即使是同一个机器的老IP正在解锁.又产生了新IP,且也被封了, 只有等老IP解封完了..会自动从内存中清除..才会有新进程来给新IP解封..
	*
	* 将最后解锁时间引入进来,是为了解决:
	* 当多进程执行的时候..有些后执行的进程会将已完成解封的 source再保存进来..这样就会导致.此机器一直被标识正在解封.就会一直无法解封.
	* 当出现以上情况时.引入最后解封时间..如果此机器的已标识正在解封..但最后解封时间是10分钟前的...则认为此IP没有标识正在解封..
	*
	* 如果正在解锁返回true
	* 否则返回false
	* 当入参错误时.会抛异常
	*
	* @access public
	* @param int $source 需要检查的机器的编号 [Must]
	* @return bool $mixed
	*/
	public function is_unlocking($source) {
		if(empty($source)){
			throw new Exception('is_unlocking param is error!');
			exit;
		}

		$cache_key = "unlocking_{$source}";
		$this->load->driver('cache', array('adapter' => 'redis'));
		if(false == ($unlocking_time = $this->cache->get($cache_key))){
			return false;
		}

		$sometime_ago = time()- 300;//5分钟前
		if($unlocking_time < $sometime_ago){//最后解封时间是5分钟前的...则认为此IP没有标识正在解封..
			//echo '2222';
			return false;
		}else{
			//echo '33333';
			return true;
		}

	}


	/**
	* 将指定source的机器标注为正在执行解锁
	*
	* @access public
	* @param int $source 需要加锁的机器的编号 [Must]
	* @return bool $mixed
	*/
	public function add_unlocking($source) {
		if(empty($source)){
			throw new Exception('add_unlocking param is error!');
			exit;
		}

		$cache_key = "unlocking_{$source}";
		$this->load->driver('cache', array('adapter' => 'redis'));


		return $unlocking_info_string = $this->cache->save($cache_key,time(),0);
	}


	/**
	* 解除指定source的机器  正在解锁标注
	*
	* @access public
	* @param int $source 需要解锁的机器的编号 [Must]
	* @return bool $mixed
	*/
	public function del_unlocking($source) {
		if(empty($source)){
			throw new Exception('del_unlocking param is error!');
			exit;
		}

		$cache_key = "unlocking_{$source}";
		$this->load->driver('cache', array('adapter' => 'redis'));

		return $this->cache->delete($cache_key);
	}


	/**
	* multi_test
	*
	* @access public
	* @return string $mixed
	*/
	public function multi_test($switch = '') {
		if($switch){
			var_dump(APPPATH);
			var_dump(realpath(APPPATH));
			//sleep(1000);
		}else{
	        $this->load->library('crawl_abstract');
	        $this->load->library('crawl_keyword_library');
			$this->load->library('crawl_sina_keyword_from_html_library');

			$this->crawl_sina_keyword_from_html_library->unlock_ip();
		}
	}
}
